package com.example.literj.kt.internal.schedulers

import com.example.literj.kt.disposables.Disposable
import com.example.literj.kt.disposables.DisposableContainer
import com.example.literj.kt.plugins.RxJavaPlugins
import java.util.concurrent.Callable
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicReferenceArray

class ScheduledRunnable(
    private val actual: Runnable, private val parent: DisposableContainer?
) : AtomicReferenceArray<Any>(3), Runnable, Callable<Any>, Disposable {
    private val PARENT_DISPOSED = Any()
    private val SYNC_DISPOSED = Any()
    private val ASYNC_DISPOSED = Any()
    private val DONE = Any()

    private val PARENT_INDEX = 0
    private val FUTURE_INDEX = 1
    private val THREAD_INDEX = 2

    init {
        this.lazySet(0, parent)
    }

    fun setFuture(f: Future<*>) {
        while (true) {
            val o = get(FUTURE_INDEX)
            if (o == DONE) {
                return
            }
            if (o == SYNC_DISPOSED) {
                f.cancel(false)
                return
            }
            if (o == ASYNC_DISPOSED) {
                f.cancel(true)
                return
            }
            if (compareAndSet(FUTURE_INDEX, o, f)) {
                return
            }
        }
    }

    override fun run() {
        lazySet(THREAD_INDEX, Thread.currentThread())
        try {
            try {
                actual.run()
            } catch (e: Throwable) {
                RxJavaPlugins.onError(e)
                throw e
            }
        } finally {
            var o = get(PARENT_INDEX)
            if (o != PARENT_DISPOSED && compareAndSet(
                    PARENT_INDEX, o, DONE
                ) && o != null
            ) {
                (o as? DisposableContainer)?.delete(this)
            }
            while (true) {
                o = get(FUTURE_INDEX)
                if (o == SYNC_DISPOSED || o == ASYNC_DISPOSED || compareAndSet(
                        FUTURE_INDEX, o, DONE
                    )
                ) {
                    break
                }
            }
            lazySet(THREAD_INDEX, null)
        }
    }

    override fun call(): Any? {
        run()
        return null
    }

    override fun dispose() {
        while (true) {
            val o = get(FUTURE_INDEX)
            if (o == DONE || o == SYNC_DISPOSED || o == ASYNC_DISPOSED) {
                break
            }
            val async = get(THREAD_INDEX) != Thread.currentThread()
            if (compareAndSet(
                    FUTURE_INDEX, o,
                    if (async) ASYNC_DISPOSED else SYNC_DISPOSED
                )
            ) {
                (o as? Future<*>)?.cancel(async)
                break
            }
        }

        while (true) {
            val o = get(PARENT_INDEX)
            if (o == DONE || o == PARENT_DISPOSED || o == null) {
                return
            }
            if (compareAndSet(
                    PARENT_INDEX, o, PARENT_DISPOSED
                )
            ) {
                (o as? DisposableContainer)?.delete(this)
                return
            }
        }
    }

    override fun isDisposed(): Boolean {
        val o = get(PARENT_INDEX)
        return o == PARENT_DISPOSED || o == DONE
    }
}